//     Underscore.js 1.8.2
//     http://underscorejs.org
//     (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
//     Underscore may be freely distributed under the MIT license.

(function() {

  // Baseline setup
  // --------------

  // Establish the root object, `window` in the browser, or `exports` on the server.
  var root = this;

  // Save the previous value of the `_` variable.
  var previousUnderscore = root._;

  // Save bytes in the minified (but not gzipped) version:
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

  // Create quick reference variables for speed access to core prototypes.
  var
    push             = ArrayProto.push,
    slice            = ArrayProto.slice,
    toString         = ObjProto.toString,
    hasOwnProperty   = ObjProto.hasOwnProperty;

  // All **ECMAScript 5** native function implementations that we hope to use
  // are declared here.
  var
    nativeIsArray      = Array.isArray,
    nativeKeys         = Object.keys,
    nativeBind         = FuncProto.bind,
    nativeCreate       = Object.create;

  // Naked function reference for surrogate-prototype-swapping.
  var Ctor = function(){};

  // Create a safe reference to the Underscore object for use below.
  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

  // Export the Underscore object for **Node.js**, with
  // backwards-compatibility for the old `require()` API. If we're in
  // the browser, add `_` as a global object.
  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

  // Current version.
  _.VERSION = '1.8.2';

  // Internal function that returns an efficient (for current engines) version
  // of the passed-in callback, to be repeatedly applied in other Underscore
  // functions.
  var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      case 2: return function(value, other) {
        return func.call(context, value, other);
      };
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

  // A mostly-internal function to generate callbacks that can be applied
  // to each element in a collection, returning the desired result — either
  // identity, an arbitrary callback, a property matcher, or a property accessor.
  var cb = function(value, context, argCount) {
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value)) return _.matcher(value);
    return _.property(value);
  };
  _.iteratee = function(value, context) {
    return cb(value, context, Infinity);
  };

  // An internal function for creating assigner functions.
  var createAssigner = function(keysFunc, undefinedOnly) {
    return function(obj) {
      var length = arguments.length;
      if (length < 2 || obj == null) return obj;
      for (var index = 1; index < length; index++) {
        var source = arguments[index],
            keys = keysFunc(source),
            l = keys.length;
        for (var i = 0; i < l; i++) {
          var key = keys[i];
          if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
        }
      }
      return obj;
    };
  };

  // An internal function for creating a new object that inherits from another.
  var baseCreate = function(prototype) {
    if (!_.isObject(prototype)) return {};
    if (nativeCreate) return nativeCreate(prototype);
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  };

  // Helper for collection methods to determine whether a collection
  // should be iterated as an array or as an object
  // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
  var isArrayLike = function(collection) {
    var length = collection != null && collection.length;
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  };

  // Collection Functions
  // --------------------

  // The cornerstone, an `each` implementation, aka `forEach`.
  // Handles raw objects in addition to array-likes. Treats all
  // sparse array-likes as if they were dense.
  _.each = _.forEach = function(obj, iteratee, context) {
    iteratee = optimizeCb(iteratee, context);
    var i, length;
    if (isArrayLike(obj)) {
      for (i = 0, length = obj.length; i < length; i++) {
        iteratee(obj[i], i, obj);
      }
    } else {
      var keys = _.keys(obj);
      for (i = 0, length = keys.length; i < length; i++) {
        iteratee(obj[keys[i]], keys[i], obj);
      }
    }
    return obj;
  };

  // Return the results of applying the iteratee to each element.
  _.map = _.collect = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length,
        results = Array(length);
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      results[index] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;
  };

  // Create a reducing function iterating left or right.
  function createReduce(dir) {
    // Optimized iterator function as using arguments.length
    // in the main function will deoptimize the, see #1991.
    function iterator(obj, iteratee, memo, keys, index, length) {
      for (; index >= 0 && index < length; index += dir) {
        var currentKey = keys ? keys[index] : index;
        memo = iteratee(memo, obj[currentKey], currentKey, obj);
      }
      return memo;
    }

    return function(obj, iteratee, memo, context) {
      iteratee = optimizeCb(iteratee, context, 4);
      var keys = !isArrayLike(obj) && _.keys(obj),
          length = (keys || obj).length,
          index = dir > 0 ? 0 : length - 1;
      // Determine the initial value if none is provided.
      if (arguments.length < 3) {
        memo = obj[keys ? keys[index] : index];
        index += dir;
      }
      return iterator(obj, iteratee, memo, keys, index, length);
    };
  }

  // **Reduce** builds up a single result from a list of values, aka `inject`,
  // or `foldl`.
  _.reduce = _.foldl = _.inject = createReduce(1);

  // The right-associative version of reduce, also known as `foldr`.
  _.reduceRight = _.foldr = createReduce(-1);

  // Return the first value which passes a truth test. Aliased as `detect`.
  _.find = _.detect = function(obj, predicate, context) {
    var key;
    if (isArrayLike(obj)) {
      key = _.findIndex(obj, predicate, context);
    } else {
      key = _.findKey(obj, predicate, context);
    }
    if (key !== void 0 && key !== -1) return obj[key];
  };

  // Return all the elements that pass a truth test.
  // Aliased as `select`.
  _.filter = _.select = function(obj, predicate, context) {
    var results = [];
    predicate = cb(predicate, context);
    _.each(obj, function(value, index, list) {
      if (predicate(value, index, list)) results.push(value);
    });
    return results;
  };

  // Return all the elements for which a truth test fails.
  _.reject = function(obj, predicate, context) {
    return _.filter(obj, _.negate(cb(predicate)), context);
  };

  // Determine whether all of the elements match a truth test.
  // Aliased as `all`.
  _.every = _.all = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      if (!predicate(obj[currentKey], currentKey, obj)) return false;
    }
    return true;
  };

  // Determine if at least one element in the object matches a truth test.
  // Aliased as `any`.
  _.some = _.any = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      if (predicate(obj[currentKey], currentKey, obj)) return true;
    }
    return false;
  };

  // Determine if the array or object contains a given value (using `===`).
  // Aliased as `includes` and `include`.
  _.contains = _.includes = _.include = function(obj, target, fromIndex) {
    if (!isArrayLike(obj)) obj = _.values(obj);
    return _.indexOf(obj, target, typeof fromIndex == 'number' && fromIndex) >= 0;
  };

  // Invoke a method (with arguments) on every item in a collection.
  _.invoke = function(obj, method) {
    var args = slice.call(arguments, 2);
    var isFunc = _.isFunction(method);
    return _.map(obj, function(value) {
      var func = isFunc ? method : value[method];
      return func == null ? func : func.apply(value, args);
    });
  };

  // Convenience version of a common use case of `map`: fetching a property.
  _.pluck = function(obj, key) {
    return _.map(obj, _.property(key));
  };

  // Convenience version of a common use case of `filter`: selecting only objects
  // containing specific `key:value` pairs.
  _.where = function(obj, attrs) {
    return _.filter(obj, _.matcher(attrs));
  };

  // Convenience version of a common use case of `find`: getting the first object
  // containing specific `key:value` pairs.
  _.findWhere = function(obj, attrs) {
    return _.find(obj, _.matcher(attrs));
  };

  // Return the maximum element (or element-based computation).
  _.max = function(obj, iteratee, context) {
    var result = -Infinity, lastComputed = -Infinity,
        value, computed;
    if (iteratee == null && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value > result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(value, index, list) {
        computed = iteratee(value, index, list);
        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
          result = value;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

  // Return the minimum element (or element-based computation).
  _.min = function(obj, iteratee, context) {
    var result = Infinity, lastComputed = Infinity,
        value, computed;
    if (iteratee == null && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value < result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(value, index, list) {
        computed = iteratee(value, index, list);
        if (computed < lastComputed || computed === Infinity && result === Infinity) {
          result = value;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

  // Shuffle a collection, using the modern version of the
  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
  _.shuffle = function(obj) {
    var set = isArrayLike(obj) ? obj : _.values(obj);
    var length = set.length;
    var shuffled = Array(length);
    for (var index = 0, rand; index < length; index++) {
      rand = _.random(0, index);
      if (rand !== index) shuffled[index] = shuffled[rand];
      shuffled[rand] = set[index];
    }
    return shuffled;
  };

  // Sample **n** random values from a collection.
  // If **n** is not specified, returns a single random element.
  // The internal `guard` argument allows it to work with `map`.
  _.sample = function(obj, n, guard) {
    if (n == null || guard) {
      if (!isArrayLike(obj)) obj = _.values(obj);
      return obj[_.random(obj.length - 1)];
    }
    return _.shuffle(obj).slice(0, Math.max(0, n));
  };

  // Sort the object's values by a criterion produced by an iteratee.
  _.sortBy = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    return _.pluck(_.map(obj, function(value, index, list) {
      return {
        value: value,
        index: index,
        criteria: iteratee(value, index, list)
      };
    }).sort(function(left, right) {
      var a = left.criteria;
      var b = right.criteria;
      if (a !== b) {
        if (a > b || a === void 0) return 1;
        if (a < b || b === void 0) return -1;
      }
      return left.index - right.index;
    }), 'value');
  };

  // An internal function used for aggregate "group by" operations.
  var group = function(behavior) {
    return function(obj, iteratee, context) {
      var result = {};
      iteratee = cb(iteratee, context);
      _.each(obj, function(value, index) {
        var key = iteratee(value, index, obj);
        behavior(result, value, key);
      });
      return result;
    };
  };

  // Groups the object's values by a criterion. Pass either a string attribute
  // to group by, or a function that returns the criterion.
  _.groupBy = group(function(result, value, key) {
    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
  });

  // Indexes the object's values by a criterion, similar to `groupBy`, but for
  // when you know that your index values will be unique.
  _.indexBy = group(function(result, value, key) {
    result[key] = value;
  });

  // Counts instances of an object that group by a certain criterion. Pass
  // either a string attribute to count by, or a function that returns the
  // criterion.
  _.countBy = group(function(result, value, key) {
    if (_.has(result, key)) result[key]++; else result[key] = 1;
  });

  // Safely create a real, live array from anything iterable.
  _.toArray = function(obj) {
    if (!obj) return [];
    if (_.isArray(obj)) return slice.call(obj);
    if (isArrayLike(obj)) return _.map(obj, _.identity);
    return _.values(obj);
  };

  // Return the number of elements in an object.
  _.size = function(obj) {
    if (obj == null) return 0;
    return isArrayLike(obj) ? obj.length : _.keys(obj).length;
  };

  // Split a collection into two arrays: one whose elements all satisfy the given
  // predicate, and one whose elements all do not satisfy the predicate.
  _.partition = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var pass = [], fail = [];
    _.each(obj, function(value, key, obj) {
      (predicate(value, key, obj) ? pass : fail).push(value);
    });
    return [pass, fail];
  };

  // Array Functions
  // ---------------

  // Get the first element of an array. Passing **n** will return the first N
  // values in the array. Aliased as `head` and `take`. The **guard** check
  // allows it to work with `_.map`.
  _.first = _.head = _.take = function(array, n, guard) {
    if (array == null) return void 0;
    if (n == null || guard) return array[0];
    return _.initial(array, array.length - n);
  };

  // Returns everything but the last entry of the array. Especially useful on
  // the arguments object. Passing **n** will return all the values in
  // the array, excluding the last N.
  _.initial = function(array, n, guard) {
    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
  };

  // Get the last element of an array. Passing **n** will return the last N
  // values in the array.
  _.last = function(array, n, guard) {
    if (array == null) return void 0;
    if (n == null || guard) return array[array.length - 1];
    return _.rest(array, Math.max(0, array.length - n));
  };

  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
  // Especially useful on the arguments object. Passing an **n** will return
  // the rest N values in the array.
  _.rest = _.tail = _.drop = function(array, n, guard) {
    return slice.call(array, n == null || guard ? 1 : n);
  };

  // Trim out all falsy values from an array.
  _.compact = function(array) {
    return _.filter(array, _.identity);
  };

  // Internal implementation of a recursive `flatten` function.
  var flatten = function(input, shallow, strict, startIndex) {
    var output = [], idx = 0;
    for (var i = startIndex || 0, length = input && input.length; i < length; i++) {
      var value = input[i];
      if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
        //flatten current level of array or arguments object
        if (!shallow) value = flatten(value, shallow, strict);
        var j = 0, len = value.length;
        output.length += len;
        while (j < len) {
          output[idx++] = value[j++];
        }
      } else if (!strict) {
        output[idx++] = value;
      }
    }
    return output;
  };

  // Flatten out an array, either recursively (by default), or just one level.
  _.flatten = function(array, shallow) {
    return flatten(array, shallow, false);
  };

  // Return a version of the array that does not contain the specified value(s).
  _.without = function(array) {
    return _.difference(array, slice.call(arguments, 1));
  };

  // Produce a duplicate-free version of the array. If the array has already
  // been sorted, you have the option of using a faster algorithm.
  // Aliased as `unique`.
  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
    if (array == null) return [];
    if (!_.isBoolean(isSorted)) {
      context = iteratee;
      iteratee = isSorted;
      isSorted = false;
    }
    if (iteratee != null) iteratee = cb(iteratee, context);
    var result = [];
    var seen = [];
    for (var i = 0, length = array.length; i < length; i++) {
      var value = array[i],
          computed = iteratee ? iteratee(value, i, array) : value;
      if (isSorted) {
        if (!i || seen !== computed) result.push(value);
        seen = computed;
      } else if (iteratee) {
        if (!_.contains(seen, computed)) {
          seen.push(computed);
          result.push(value);
        }
      } else if (!_.contains(result, value)) {
        result.push(value);
      }
    }
    return result;
  };

  // Produce an array that contains the union: each distinct element from all of
  // the passed-in arrays.
  _.union = function() {
    return _.uniq(flatten(arguments, true, true));
  };

  // Produce an array that contains every item shared between all the
  // passed-in arrays.
  _.intersection = function(array) {
    if (array == null) return [];
    var result = [];
    var argsLength = arguments.length;
    for (var i = 0, length = array.length; i < length; i++) {
      var item = array[i];
      if (_.contains(result, item)) continue;
      for (var j = 1; j < argsLength; j++) {
        if (!_.contains(arguments[j], item)) break;
      }
      if (j === argsLength) result.push(item);
    }
    return result;
  };

  // Take the difference between one array and a number of other arrays.
  // Only the elements present in just the first array will remain.
  _.difference = function(array) {
    var rest = flatten(arguments, true, true, 1);
    return _.filter(array, function(value){
      return !_.contains(rest, value);
    });
  };

  // Zip together multiple lists into a single array -- elements that share
  // an index go together.
  _.zip = function() {
    return _.unzip(arguments);
  };

  // Complement of _.zip. Unzip accepts an array of arrays and groups
  // each array's elements on shared indices
  _.unzip = function(array) {
    var length = array && _.max(array, 'length').length || 0;
    var result = Array(length);

    for (var index = 0; index < length; index++) {
      result[index] = _.pluck(array, index);
    }
    return result;
  };

  // Converts lists into objects. Pass either a single array of `[key, value]`
  // pairs, or two parallel arrays of the same length -- one of keys, and one of
  // the corresponding values.
  _.object = function(list, values) {
    var result = {};
    for (var i = 0, length = list && list.length; i < length; i++) {
      if (values) {
        result[list[i]] = values[i];
      } else {
        result[list[i][0]] = list[i][1];
      }
    }
    return result;
  };

  // Return the position of the first occurrence of an item in an array,
  // or -1 if the item is not included in the array.
  // If the array is large and already in sort order, pass `true`
  // for **isSorted** to use binary search.
  _.indexOf = function(array, item, isSorted) {
    var i = 0, length = array && array.length;
    if (typeof isSorted == 'number') {
      i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
    } else if (isSorted && length) {
      i = _.sortedIndex(array, item);
      return array[i] === item ? i : -1;
    }
    if (item !== item) {
      return _.findIndex(slice.call(array, i), _.isNaN);
    }
    for (; i < length; i++) if (array[i] === item) return i;
    return -1;
  };

  _.lastIndexOf = function(array, item, from) {
    var idx = array ? array.length : 0;
    if (typeof from == 'number') {
      idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
    }
    if (item !== item) {
      return _.findLastIndex(slice.call(array, 0, idx), _.isNaN);
    }
    while (--idx >= 0) if (array[idx] === item) return idx;
    return -1;
  };

  // Generator function to create the findIndex and findLastIndex functions
  function createIndexFinder(dir) {
    return function(array, predicate, context) {
      predicate = cb(predicate, context);
      var length = array != null && array.length;
      var index = dir > 0 ? 0 : length - 1;
      for (; index >= 0 && index < length; index += dir) {
        if (predicate(array[index], index, array)) return index;
      }
      return -1;
    };
  }

  // Returns the first index on an array-like that passes a predicate test
  _.findIndex = createIndexFinder(1);

  _.findLastIndex = createIndexFinder(-1);

  // Use a comparator function to figure out the smallest index at which
  // an object should be inserted so as to maintain order. Uses binary search.
  _.sortedIndex = function(array, obj, iteratee, context) {
    iteratee = cb(iteratee, context, 1);
    var value = iteratee(obj);
    var low = 0, high = array.length;
    while (low < high) {
      var mid = Math.floor((low + high) / 2);
      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
    }
    return low;
  };

  // Generate an integer Array containing an arithmetic progression. A port of
  // the native Python `range()` function. See
  // [the Python documentation](http://docs.python.org/library/functions.html#range).
  _.range = function(start, stop, step) {
    if (arguments.length <= 1) {
      stop = start || 0;
      start = 0;
    }
    step = step || 1;

    var length = Math.max(Math.ceil((stop - start) / step), 0);
    var range = Array(length);

    for (var idx = 0; idx < length; idx++, start += step) {
      range[idx] = start;
    }

    return range;
  };

  // Function (ahem) Functions
  // ------------------

  // Determines whether to execute a function as a constructor
  // or a normal function with the provided arguments
  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
    var self = baseCreate(sourceFunc.prototype);
    var result = sourceFunc.apply(self, args);
    if (_.isObject(result)) return result;
    return self;
  };

  // Create a function bound to a given object (assigning `this`, and arguments,
  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
  // available.
  _.bind = function(func, context) {
    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
    var args = slice.call(arguments, 2);
    var bound = function() {
      return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
    };
    return bound;
  };

  // Partially apply a function by creating a version that has had some of its
  // arguments pre-filled, without changing its dynamic `this` context. _ acts
  // as a placeholder, allowing any combination of arguments to be pre-filled.
  _.partial = function(func) {
    var boundArgs = slice.call(arguments, 1);
    var bound = function() {
      var position = 0, length = boundArgs.length;
      var args = Array(length);
      for (var i = 0; i < length; i++) {
        args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
      }
      while (position < arguments.length) args.push(arguments[position++]);
      return executeBound(func, bound, this, this, args);
    };
    return bound;
  };

  // Bind a number of an object's methods to that object. Remaining arguments
  // are the method names to be bound. Useful for ensuring that all callbacks
  // defined on an object belong to it.
  _.bindAll = function(obj) {
    var i, length = arguments.length, key;
    if (length <= 1) throw new Error('bindAll must be passed function names');
    for (i = 1; i < length; i++) {
      key = arguments[i];
      obj[key] = _.bind(obj[key], obj);
    }
    return obj;
  };

  // Memoize an expensive function by storing its results.
  _.memoize = function(func, hasher) {
    var memoize = function(key) {
      var cache = memoize.cache;
      var address = '' + (hasher ? hasher.apply(this, arguments) : key);
      if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
      return cache[address];
    };
    memoize.cache = {};
    return memoize;
  };

  // Delays a function for the given number of milliseconds, and then calls
  // it with the arguments supplied.
  _.delay = function(func, wait) {
    var args = slice.call(arguments, 2);
    return setTimeout(function(){
      return func.apply(null, args);
    }, wait);
  };

  // Defers a function, scheduling it to run after the current call stack has
  // cleared.
  _.defer = _.partial(_.delay, _, 1);

  // Returns a function, that, when invoked, will only be triggered at most once
  // during a given window of time. Normally, the throttled function will run
  // as much as it can, without ever going more than once per `wait` duration;
  // but if you'd like to disable the execution on the leading edge, pass
  // `{leading: false}`. To disable execution on the trailing edge, ditto.
  _.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    if (!options) options = {};
    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

  // Returns a function, that, as long as it continues to be invoked, will not
  // be triggered. The function will be called after it stops being called for
  // N milliseconds. If `immediate` is passed, trigger the function on the
  // leading edge, instead of the trailing.
  _.debounce = function(func, wait, immediate) {
    var timeout, args, context, timestamp, result;

    var later = function() {
      var last = _.now() - timestamp;

      if (last < wait && last >= 0) {
        timeout = setTimeout(later, wait - last);
      } else {
        timeout = null;
        if (!immediate) {
          result = func.apply(context, args);
          if (!timeout) context = args = null;
        }
      }
    };

    return function() {
      context = this;
      args = arguments;
      timestamp = _.now();
      var callNow = immediate && !timeout;
      if (!timeout) timeout = setTimeout(later, wait);
      if (callNow) {
        result = func.apply(context, args);
        context = args = null;
      }

      return result;
    };
  };

  // Returns the first function passed as an argument to the second,
  // allowing you to adjust arguments, run code before and after, and
  // conditionally execute the original function.
  _.wrap = function(func, wrapper) {
    return _.partial(wrapper, func);
  };

  // Returns a negated version of the passed-in predicate.
  _.negate = function(predicate) {
    return function() {
      return !predicate.apply(this, arguments);
    };
  };

  // Returns a function that is the composition of a list of functions, each
  // consuming the return value of the function that follows.
  _.compose = function() {
    var args = arguments;
    var start = args.length - 1;
    return function() {
      var i = start;
      var result = args[start].apply(this, arguments);
      while (i--) result = args[i].call(this, result);
      return result;
    };
  };

  // Returns a function that will only be executed on and after the Nth call.
  _.after = function(times, func) {
    return function() {
      if (--times < 1) {
        return func.apply(this, arguments);
      }
    };
  };

  // Returns a function that will only be executed up to (but not including) the Nth call.
  _.before = function(times, func) {
    var memo;
    return function() {
      if (--times > 0) {
        memo = func.apply(this, arguments);
      }
      if (times <= 1) func = null;
      return memo;
    };
  };

  // Returns a function that will be executed at most one time, no matter how
  // often you call it. Useful for lazy initialization.
  _.once = _.partial(_.before, 2);

  // Object Functions
  // ----------------

  // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
  var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
                      'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

  function collectNonEnumProps(obj, keys) {
    var nonEnumIdx = nonEnumerableProps.length;
    var constructor = obj.constructor;
    var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;

    // Constructor is a special case.
    var prop = 'constructor';
    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

    while (nonEnumIdx--) {
      prop = nonEnumerableProps[nonEnumIdx];
      if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
        keys.push(prop);
      }
    }
  }

  // Retrieve the names of an object's own properties.
  // Delegates to **ECMAScript 5**'s native `Object.keys`
  _.keys = function(obj) {
    if (!_.isObject(obj)) return [];
    if (nativeKeys) return nativeKeys(obj);
    var keys = [];
    for (var key in obj) if (_.has(obj, key)) keys.push(key);
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
  };

  // Retrieve all the property names of an object.
  _.allKeys = function(obj) {
    if (!_.isObject(obj)) return [];
    var keys = [];
    for (var key in obj) keys.push(key);
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
  };

  // Retrieve the values of an object's properties.
  _.values = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var values = Array(length);
    for (var i = 0; i < length; i++) {
      values[i] = obj[keys[i]];
    }
    return values;
  };

  // Returns the results of applying the iteratee to each element of the object
  // In contrast to _.map it returns an object
  _.mapObject = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys =  _.keys(obj),
          length = keys.length,
          results = {},
          currentKey;
      for (var index = 0; index < length; index++) {
        currentKey = keys[index];
        results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
      }
      return results;
  };

  // Convert an object into a list of `[key, value]` pairs.
  _.pairs = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var pairs = Array(length);
    for (var i = 0; i < length; i++) {
      pairs[i] = [keys[i], obj[keys[i]]];
    }
    return pairs;
  };

  // Invert the keys and values of an object. The values must be serializable.
  _.invert = function(obj) {
    var result = {};
    var keys = _.keys(obj);
    for (var i = 0, length = keys.length; i < length; i++) {
      result[obj[keys[i]]] = keys[i];
    }
    return result;
  };

  // Return a sorted list of the function names available on the object.
  // Aliased as `methods`
  _.functions = _.methods = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };

  // Extend a given object with all the properties in passed-in object(s).
  _.extend = createAssigner(_.allKeys);

  // Assigns a given object with all the own properties in the passed-in object(s)
  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
  _.extendOwn = _.assign = createAssigner(_.keys);

  // Returns the first key on an object that passes a predicate test
  _.findKey = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = _.keys(obj), key;
    for (var i = 0, length = keys.length; i < length; i++) {
      key = keys[i];
      if (predicate(obj[key], key, obj)) return key;
    }
  };

  // Return a copy of the object only containing the whitelisted properties.
  _.pick = function(object, oiteratee, context) {
    var result = {}, obj = object, iteratee, keys;
    if (obj == null) return result;
    if (_.isFunction(oiteratee)) {
      keys = _.allKeys(obj);
      iteratee = optimizeCb(oiteratee, context);
    } else {
      keys = flatten(arguments, false, false, 1);
      iteratee = function(value, key, obj) { return key in obj; };
      obj = Object(obj);
    }
    for (var i = 0, length = keys.length; i < length; i++) {
      var key = keys[i];
      var value = obj[key];
      if (iteratee(value, key, obj)) result[key] = value;
    }
    return result;
  };

   // Return a copy of the object without the blacklisted properties.
  _.omit = function(obj, iteratee, context) {
    if (_.isFunction(iteratee)) {
      iteratee = _.negate(iteratee);
    } else {
      var keys = _.map(flatten(arguments, false, false, 1), String);
      iteratee = function(value, key) {
        return !_.contains(keys, key);
      };
    }
    return _.pick(obj, iteratee, context);
  };

  // Fill in a given object with default properties.
  _.defaults = createAssigner(_.allKeys, true);

  // Creates an object that inherits from the given prototype object.
  // If additional properties are provided then they will be added to the
  // created object.
  _.create = function(prototype, props) {
    var result = baseCreate(prototype);
    if (props) _.extendOwn(result, props);
    return result;
  };

  // Create a (shallow-cloned) duplicate of an object.
  _.clone = function(obj) {
    if (!_.isObject(obj)) return obj;
    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  };

  // Invokes interceptor with the obj, and then returns obj.
  // The primary purpose of this method is to "tap into" a method chain, in
  // order to perform operations on intermediate results within the chain.
  _.tap = function(obj, interceptor) {
    interceptor(obj);
    return obj;
  };

  // Returns whether an object has a given set of `key:value` pairs.
  _.isMatch = function(object, attrs) {
    var keys = _.keys(attrs), length = keys.length;
    if (object == null) return !length;
    var obj = Object(object);
    for (var i = 0; i < length; i++) {
      var key = keys[i];
      if (attrs[key] !== obj[key] || !(key in obj)) return false;
    }
    return true;
  };


  // Internal recursive comparison function for `isEqual`.
  var eq = function(a, b, aStack, bStack) {
    // Identical objects are equal. `0 === -0`, but they aren't identical.
    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
    if (a === b) return a !== 0 || 1 / a === 1 / b;
    // A strict comparison is necessary because `null == undefined`.
    if (a == null || b == null) return a === b;
    // Unwrap any wrapped objects.
    if (a instanceof _) a = a._wrapped;
    if (b instanceof _) b = b._wrapped;
    // Compare `[[Class]]` names.
    var className = toString.call(a);
    if (className !== toString.call(b)) return false;
    switch (className) {
      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
      case '[object RegExp]':
      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
      case '[object String]':
        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
        // equivalent to `new String("5")`.
        return '' + a === '' + b;
      case '[object Number]':
        // `NaN`s are equivalent, but non-reflexive.
        // Object(NaN) is equivalent to NaN
        if (+a !== +a) return +b !== +b;
        // An `egal` comparison is performed for other numeric values.
        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
      case '[object Date]':
      case '[object Boolean]':
        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
        // millisecond representations. Note that invalid dates with millisecond representations
        // of `NaN` are not equivalent.
        return +a === +b;
    }

    var areArrays = className === '[object Array]';
    if (!areArrays) {
      if (typeof a != 'object' || typeof b != 'object') return false;

      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
      // from different frames are.
      var aCtor = a.constructor, bCtor = b.constructor;
      if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
                               _.isFunction(bCtor) && bCtor instanceof bCtor)
                          && ('constructor' in a && 'constructor' in b)) {
        return false;
      }
    }
    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
    
    // Initializing stack of traversed objects.
    // It's done here since we only need them for objects and arrays comparison.
    aStack = aStack || [];
    bStack = bStack || [];
    var length = aStack.length;
    while (length--) {
      // Linear search. Performance is inversely proportional to the number of
      // unique nested structures.
      if (aStack[length] === a) return bStack[length] === b;
    }

    // Add the first object to the stack of traversed objects.
    aStack.push(a);
    bStack.push(b);

    // Recursively compare objects and arrays.
    if (areArrays) {
      // Compare array lengths to determine if a deep comparison is necessary.
      length = a.length;
      if (length !== b.length) return false;
      // Deep compare the contents, ignoring non-numeric properties.
      while (length--) {
        if (!eq(a[length], b[length], aStack, bStack)) return false;
      }
    } else {
      // Deep compare objects.
      var keys = _.keys(a), key;
      length = keys.length;
      // Ensure that both objects contain the same number of properties before comparing deep equality.
      if (_.keys(b).length !== length) return false;
      while (length--) {
        // Deep compare each member
        key = keys[length];
        if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
      }
    }
    // Remove the first object from the stack of traversed objects.
    aStack.pop();
    bStack.pop();
    return true;
  };

  // Perform a deep comparison to check if two objects are equal.
  _.isEqual = function(a, b) {
    return eq(a, b);
  };

  // Is a given array, string, or object empty?
  // An "empty" object has no enumerable own-properties.
  _.isEmpty = function(obj) {
    if (obj == null) return true;
    if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
    return _.keys(obj).length === 0;
  };

  // Is a given value a DOM element?
  _.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
  };

  // Is a given value an array?
  // Delegates to ECMA5's native Array.isArray
  _.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) === '[object Array]';
  };

  // Is a given variable an object?
  _.isObject = function(obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object' && !!obj;
  };

  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) === '[object ' + name + ']';
    };
  });

  // Define a fallback version of the method in browsers (ahem, IE < 9), where
  // there isn't any inspectable "Arguments" type.
  if (!_.isArguments(arguments)) {
    _.isArguments = function(obj) {
      return _.has(obj, 'callee');
    };
  }

  // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
  // IE 11 (#1621), and in Safari 8 (#1929).
  if (typeof /./ != 'function' && typeof Int8Array != 'object') {
    _.isFunction = function(obj) {
      return typeof obj == 'function' || false;
    };
  }

  // Is a given object a finite number?
  _.isFinite = function(obj) {
    return isFinite(obj) && !isNaN(parseFloat(obj));
  };

  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
  _.isNaN = function(obj) {
    return _.isNumber(obj) && obj !== +obj;
  };

  // Is a given value a boolean?
  _.isBoolean = function(obj) {
    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
  };

  // Is a given value equal to null?
  _.isNull = function(obj) {
    return obj === null;
  };

  // Is a given variable undefined?
  _.isUndefined = function(obj) {
    return obj === void 0;
  };

  // Shortcut function for checking if an object has a given property directly
  // on itself (in other words, not on a prototype).
  _.has = function(obj, key) {
    return obj != null && hasOwnProperty.call(obj, key);
  };

  // Utility Functions
  // -----------------

  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  // previous owner. Returns a reference to the Underscore object.
  _.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };

  // Keep the identity function around for default iteratees.
  _.identity = function(value) {
    return value;
  };

  // Predicate-generating functions. Often useful outside of Underscore.
  _.constant = function(value) {
    return function() {
      return value;
    };
  };

  _.noop = function(){};

  _.property = function(key) {
    return function(obj) {
      return obj == null ? void 0 : obj[key];
    };
  };

  // Generates a function for a given object that returns a given property.
  _.propertyOf = function(obj) {
    return obj == null ? function(){} : function(key) {
      return obj[key];
    };
  };

  // Returns a predicate for checking whether an object has a given set of 
  // `key:value` pairs.
  _.matcher = _.matches = function(attrs) {
    attrs = _.extendOwn({}, attrs);
    return function(obj) {
      return _.isMatch(obj, attrs);
    };
  };

  // Run a function **n** times.
  _.times = function(n, iteratee, context) {
    var accum = Array(Math.max(0, n));
    iteratee = optimizeCb(iteratee, context, 1);
    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
    return accum;
  };

  // Return a random integer between min and max (inclusive).
  _.random = function(min, max) {
    if (max == null) {
      max = min;
      min = 0;
    }
    return min + Math.floor(Math.random() * (max - min + 1));
  };

  // A (possibly faster) way to get the current timestamp as an integer.
  _.now = Date.now || function() {
    return new Date().getTime();
  };

   // List of HTML entities for escaping.
  var escapeMap = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '`': '&#x60;'
  };
  var unescapeMap = _.invert(escapeMap);

  // Functions for escaping and unescaping strings to/from HTML interpolation.
  var createEscaper = function(map) {
    var escaper = function(match) {
      return map[match];
    };
    // Regexes for identifying a key that needs to be escaped
    var source = '(?:' + _.keys(map).join('|') + ')';
    var testRegexp = RegExp(source);
    var replaceRegexp = RegExp(source, 'g');
    return function(string) {
      string = string == null ? '' : '' + string;
      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
    };
  };
  _.escape = createEscaper(escapeMap);
  _.unescape = createEscaper(unescapeMap);

  // If the value of the named `property` is a function then invoke it with the
  // `object` as context; otherwise, return it.
  _.result = function(object, property, fallback) {
    var value = object == null ? void 0 : object[property];
    if (value === void 0) {
      value = fallback;
    }
    return _.isFunction(value) ? value.call(object) : value;
  };

  // Generate a unique integer id (unique within the entire client session).
  // Useful for temporary DOM ids.
  var idCounter = 0;
  _.uniqueId = function(prefix) {
    var id = ++idCounter + '';
    return prefix ? prefix + id : id;
  };

  // By default, Underscore uses ERB-style template delimiters, change the
  // following template settings to use alternative delimiters.
  _.templateSettings = {
    evaluate    : /<%([\s\S]+?)%>/g,
    interpolate : /<%=([\s\S]+?)%>/g,
    escape      : /<%-([\s\S]+?)%>/g
  };

  // When customizing `templateSettings`, if you don't want to define an
  // interpolation, evaluation or escaping regex, we need one that is
  // guaranteed not to match.
  var noMatch = /(.)^/;

  // Certain characters need to be escaped so that they can be put into a
  // string literal.
  var escapes = {
    "'":      "'",
    '\\':     '\\',
    '\r':     'r',
    '\n':     'n',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  var escaper = /\\|'|\r|\n|\u2028|\u2029/g;

  var escapeChar = function(match) {
    return '\\' + escapes[match];
  };

  // JavaScript micro-templating, similar to John Resig's implementation.
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
  // and correctly escapes quotes within interpolated code.
  // NB: `oldSettings` only exists for backwards compatibility.
  _.template = function(text, settings, oldSettings) {
    if (!settings && oldSettings) settings = oldSettings;
    settings = _.defaults({}, settings, _.templateSettings);

    // Combine delimiters into one regular expression via alternation.
    var matcher = RegExp([
      (settings.escape || noMatch).source,
      (settings.interpolate || noMatch).source,
      (settings.evaluate || noMatch).source
    ].join('|') + '|$', 'g');

    // Compile the template source, escaping string literals appropriately.
    var index = 0;
    var source = "__p+='";
    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
      source += text.slice(index, offset).replace(escaper, escapeChar);
      index = offset + match.length;

      if (escape) {
        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
      } else if (interpolate) {
        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
      } else if (evaluate) {
        source += "';\n" + evaluate + "\n__p+='";
      }

      // Adobe VMs need the match returned to produce the correct offest.
      return match;
    });
    source += "';\n";

    // If a variable is not specified, place data values in local scope.
    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

    source = "var __t,__p='',__j=Array.prototype.join," +
      "print=function(){__p+=__j.call(arguments,'');};\n" +
      source + 'return __p;\n';

    try {
      var render = new Function(settings.variable || 'obj', '_', source);
    } catch (e) {
      e.source = source;
      throw e;
    }

    var template = function(data) {
      return render.call(this, data, _);
    };

    // Provide the compiled source as a convenience for precompilation.
    var argument = settings.variable || 'obj';
    template.source = 'function(' + argument + '){\n' + source + '}';

    return template;
  };

  // Add a "chain" function. Start chaining a wrapped Underscore object.
  _.chain = function(obj) {
    var instance = _(obj);
    instance._chain = true;
    return instance;
  };

  // OOP
  // ---------------
  // If Underscore is called as a function, it returns a wrapped object that
  // can be used OO-style. This wrapper holds altered versions of all the
  // underscore functions. Wrapped objects may be chained.

  // Helper function to continue chaining intermediate results.
  var result = function(instance, obj) {
    return instance._chain ? _(obj).chain() : obj;
  };

  // Add your own custom functions to the Underscore object.
  _.mixin = function(obj) {
    _.each(_.functions(obj), function(name) {
      var func = _[name] = obj[name];
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        return result(this, func.apply(_, args));
      };
    });
  };

  // Add all of the Underscore functions to the wrapper object.
  _.mixin(_);

  // Add all mutator Array functions to the wrapper.
  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      var obj = this._wrapped;
      method.apply(obj, arguments);
      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
      return result(this, obj);
    };
  });

  // Add all accessor Array functions to the wrapper.
  _.each(['concat', 'join', 'slice'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      return result(this, method.apply(this._wrapped, arguments));
    };
  });

  // Extracts the result from a wrapped and chained object.
  _.prototype.value = function() {
    return this._wrapped;
  };

  // Provide unwrapping proxy for some methods used in engine operations
  // such as arithmetic and JSON stringification.
  _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
  
  _.prototype.toString = function() {
    return '' + this._wrapped;
  };

  // AMD registration happens at the end for compatibility with AMD loaders
  // that may not enforce next-turn semantics on modules. Even though general
  // practice for AMD registration is to be anonymous, underscore registers
  // as a named module because, like jQuery, it is a base library that is
  // popular enough to be bundled in a third party lib, but not be part of
  // an AMD load request. Those cases could generate an error when an
  // anonymous define() is called outside of a loader request.
  if (typeof define === 'function' && define.amd) {
    define('underscore', [], function() {
      return _;
    });
  }
}.call(this));

/**
 * @author yangyj
 * @desc 数组操作
 * @depends widget.js
 */
(function(window){
	var underscore = vita.widget.require("underscore");
	vita.widget("arrayUtil",{
		//返回array（数组）的第一个元素。传递 n参数将返回数组中从第一个元素开始的n个元素//返回数组中前 n 个元素
		//first(array, [n])
		/*
		 _.first([5, 4, 3, 2, 1]);
		 => 5
		 */
		first : function(array, n){
			return underscore.first(array, n);
		},
		//返回数组中除了最后一个元素外的其他全部元素。 在arguments对象上特别有用。传递 n参数将从结果中排除从最后一个开始的n个元素//排除数组后面的 n 个元素
		//initial(array, [n])
		/*
		 _.initial([5, 4, 3, 2, 1]);
		 => [5, 4, 3, 2]
		 */
		initial : function(array, n){
			return underscore.initial(array, n);
		},
		//返回array（数组）的最后一个元素。传递 n参数将返回数组中从最后一个元素开始的n个元素（愚人码头注：返回数组里的后面的n个元素）。
		//last(array, [n])
		/*
		 _.last([5, 4, 3, 2, 1]);
		 => 1
		 */
		last : function(array, n){
			return underscore.last(array, n);
		},
		//返回数组中除了第一个元素外的其他全部元素。传递 index 参数将返回从index开始的剩余所有元素
		//rest(array, [index])
		/*
		 _.rest([5, 4, 3, 2, 1]);
		 => [4, 3, 2, 1]
		 */
		rest : function(array, index){
			return underscore.rest(array, index);
		},
		//返回一个除去所有false值的 array副本。 在javascript中, false, null, 0, "", undefined 和 NaN 都是false值.
		//compact(array)
		/*
		 _.compact([0, 1, false, 2, '', 3]);
		 => [1, 2, 3]
		 */
		compact : function(array){
			return underscore.compact(array);
		},
		//将一个嵌套多层的数组 array（数组） (嵌套可以是任何层数)转换为只有一层的数组。 如果你传递 shallow参数，数组将只减少一维的嵌套
		//flatten(array, [shallow])
		/*
		 _.flatten([1, [2], [3, [[4]]]]);
		 => [1, 2, 3, 4];

		 _.flatten([1, [2], [3, [[4]]]], true);
		 => [1, 2, 3, [[4]]];
		 */
		flatten : function(array, shallow){
			return underscore.flatten(array, shallow);
		},
		//返回一个删除所有values值后的 array副本//使用===表达式做相等测试
		//without(array, *values)
		/*
		 _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
		 => [2, 3, 4]
		 */
		without : function(){
			return underscore.without.apply(underscore,arguments);
		},
		//返回传入的 arrays（数组）并集：按顺序返回，返回数组的元素是唯一的，可以传入一个或多个 arrays（数组）。
		//union(*arrays)
		/*
		 _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
		 => [1, 2, 3, 101, 10]
		 */
		union : function(){
			return underscore.union.apply(underscore,arguments);
		},
		//返回传入 arrays（数组）交集。结果中的每个值是存在于传入的每个arrays（数组）里。
		//intersection(*arrays)
		/*
		 _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
		 => [1, 2]
		 */
		intersection : function(){
			return underscore.intersection.apply(underscore,arguments);
		},
		//类似于without，但返回的值来自array参数数组，并且不存在于other 数组.
		//difference(array, *others)
		/*
		 _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
		 => [1, 3, 4]
		 */
		difference : function(){
			return underscore.difference.apply(underscore,arguments);
		},
		//返回 array去重后的副本, 使用 === 做相等测试. 如果您确定 array 已经排序, 那么给 isSorted 参数传递 true值, 此函数将运行的更快的算法. 如果要处理对象元素, 传递 iteratee函数来获取要对比的属性.
		//uniq(array, [isSorted], [iteratee])
		/*
		 _.uniq([1, 2, 1, 3, 1, 4]);
		 => [1, 2, 3, 4]
		 */
		uniq : function(array, isSorted, iteratee){
			return underscore.uniq(array, isSorted, iteratee);
		},
		//将 每个arrays中相应位置的值合并在一起。在合并分开保存的数据时很有用. 如果你用来处理矩阵嵌套数组时, _.zip.apply 可以做类似的效果。
		//zip(*arrays)
		/*
		 _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
		 => [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
		 */
		zip : function(){
			return underscore.zip.apply(underscore,arguments);
		},
		//与zip功能相反的函数，给定若干arrays，返回一串联的新数组，其第一元素个包含所有的输入数组的第一元素，其第二包含了所有的第二元素，依此类推。通过apply用于传递数组的数组
		//unzip(*arrays)
		/*
		 _.unzip([['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]])
		 => ["moe", 30, true], ["larry", 40, false], ["curly", 50, false]
		 */
		unzip : function(){
			return underscore.unzip.apply(underscore,arguments);
		},
		//将数组转换为对象。传递任何一个单独[key, value]对的列表，或者一个键的列表和一个值得列表。 如果存在重复键，最后一个值将被返回。
		//object(list, [values])
		/*
		 _.object(['moe', 'larry', 'curly'], [30, 40, 50]);
		 => {moe: 30, larry: 40, curly: 50}

		 _.object([['moe', 30], ['larry', 40], ['curly', 50]]);
		 => {moe: 30, larry: 40, curly: 50}
		 */
		object : function(list, values){
			return underscore.object(list, values);
		},
		//返回value在该 array 中的索引值，如果value不存在 array中就返回-1。使用原生的indexOf 函数，除非它失效。如果您正在使用一个大数组，你知道数组已经排序，传递true给isSorted将更快的用二进制搜索..,或者，传递一个数字作为第三个参数，为了在给定的索引的数组中寻找第一个匹配值
		//indexOf(array, value, [isSorted])
		/*
		 _.indexOf([1, 2, 3], 2);
		 => 1
		 */
		indexOf : function(array, value, isSorted){
			return underscore.indexOf(array, value, isSorted);
		},
		//返回value在该 array 中的从最后开始的索引值，如果value不存在 array中就返回-1。如果支持原生的lastIndexOf，将使用原生的lastIndexOf函数。传递fromIndex将从你给定的索性值开始搜索
		//lastIndexOf(array, value, [fromIndex])
		/*
		 _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
		 => 4
		 */
		lastIndexOf : function(array, value, fromIndex){
			return underscore.lastIndexOf(array, value, fromIndex);
		},
		//使用二分查找确定value在list中的位置序号，value按此序号插入能保持list原有的排序。如果提供iterator函数，iterator将作为list排序的依据，包括你传递的value 。iterator也可以是字符串的属性名用来排序(比如length)。
		//sortedIndex(list, value, [iteratee], [context])
		/*
		 _.sortedIndex([10, 20, 30, 40, 50], 35);
		 => 3

		 var stooges = [{name: 'moe', age: 40}, {name: 'curly', age: 60}];
		 _.sortedIndex(stooges, {name: 'larry', age: 50}, 'age');
		 => 1
		 */
		sortedIndex : function(list, value, iteratee, context){
			return underscore.sortedIndex(list, value, iteratee, context);
		},
		//类似于_.indexOf，当predicate通过真检查时，返回第一个索引值；否则返回-1。
		//findIndex(array, predicate, [context])
		/*
		 _.findIndex([4, 6, 8, 12], isPrime);
		 => -1 // not found
		 _.findIndex([4, 6, 7, 12], isPrime);
		 => 2
		 */
		findIndex : function(array, predicate, context){
			return underscore.findIndex(array, predicate, context);
		},
		//和_.findIndex类似，但反向迭代数组，当predicate通过真检查时，最接近末端的索引值将被返回。
		//findLastIndex(array, predicate, [context])
		/*
		 var users = [{'id': 1, 'name': 'Bob', 'last': 'Brown'},
		 {'id': 2, 'name': 'Ted', 'last': 'White'},
		 {'id': 3, 'name': 'Frank', 'last': 'James'},
		 {'id': 4, 'name': 'Ted', 'last': 'Jones'}];
		 _.findLastIndex(users, {
		 name: 'Ted'
		 });
		 => 3
		 */
		findLastIndex : function(array, predicate, context){
			return underscore.findLastIndex(array, predicate, context);
		},
		//一个用来创建整数灵活编号的列表的函数，便于each 和 map循环。如果省略start则默认为 0；step 默认为 1.返回一个从start 到stop的整数的列表，用step来增加 （或减少）独占。值得注意的是，如果stop值在start前面（也就是stop值小于start值），那么值域会被认为是零长度，而不是负增长。-如果你要一个负数的值域 ，请使用负数step
		//range([start], stop, [step])
		/*
		 _.range(10);
		 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
		 _.range(1, 11);
		 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
		 _.range(0, 30, 5);
		 => [0, 5, 10, 15, 20, 25]
		 _.range(0, -10, -1);
		 => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
		 _.range(0);
		 => []
		 */
		range : function(start, stop, step){
			return underscore.range(start, stop, step);
		}
	});
})(window);
/**
 * @author yangyj
 * @desc 链式语法
 * @depends widget.js
 */
(function(window){
	var underscore = vita.widget.require("underscore");
	vita.widget("chain",{
		//返回一个封装的对象. 在封装的对象上调用方法会返回封装的对象本身, 直道 value 方法调用为止.
		//chain(obj)
		/*
		 var stooges = [{name: 'curly', age: 25}, {name: 'moe', age: 21}, {name: 'larry', age: 23}];
		 var youngest = _.chain(stooges)
		 .sortBy(function(stooge){ return stooge.age; })
		 .map(function(stooge){ return stooge.name + ' is ' + stooge.age; })
		 .first()
		 .value();
		 => "moe is 21"
		 */
		chain : function(obj){
			return underscore.chain(obj);
		}
	});
})(window);
/**
 * @author yangyj
 * @desc 集合操作
 * @depends widget.js
 */
(function(window){
	var underscore = vita.widget.require("underscore");
	vita.widget("collectionUtil",{
		//遍历list中的所有元素，按顺序用遍历输出每个元素。如果传递了context参数，则把iteratee绑定到context对象上。每次调用iteratee都会传递三个参数：(element, index, list)。如果list是个JavaScript对象，iteratee的参数是 (value, key, list))。返回list以方便链式调用//如果存在原生的forEach方法，Underscore就使用它代替
		//each(list, iteratee, [context])
		/*
		 _.each([1, 2, 3], alert);
		 => alerts each number in turn...
		 _.each({one: 1, two: 2, three: 3}, alert);
		 => alerts each number value in turn...
		 */
		each : function(list, iteratee, context){
			return underscore.each(list, iteratee, context);
		},
		//通过转换函数(iteratee迭代器)映射列表中的每个值产生价值的新数组。iteratee传递三个参数：value，然后是迭代 index(或 key 愚人码头注：如果list是个JavaScript对象是，这个参数就是key)，最后一个是引用指向整个list
		//map(list, iteratee, [context])
		/*
		 _.map([1, 2, 3], function(num){ return num * 3; });
		 => [3, 6, 9]
		 _.map({one: 1, two: 2, three: 3}, function(num, key){ return num * 3; });
		 => [3, 6, 9]
		 _.map([[1, 2], [3, 4]], _.first);
		 => [1, 3]
		 */
		map : function(list, iteratee, context){
			return underscore.map(list, iteratee, context);
		},
		//别名为 inject 和 foldl, reduce方法把list中元素归结为一个单独的数值。Memo是reduce函数的初始值，reduce的每一步都需要由iteratee返回。这个迭代传递4个参数：memo,value 和 迭代的index（或者 key）和最后一个引用的整个 list
		//reduce(list, iteratee, [memo], [context])
		/*
		 var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
		 => 6
		 */
		reduce : function(list, iteratee, memo, context){
			return underscore.reduce(list, iteratee, memo, context);
		},
		//reducRight是从右侧开始组合的元素的reduce函数，如果存在JavaScript 1.8版本的reduceRight，则用其代替
		//reduceRight(list, iteratee, memo, [context])
		/*
		 var list = [[0, 1], [2, 3], [4, 5]];
		 var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
		 => [4, 5, 2, 3, 0, 1]
		 */
		reduceRight : function(list, iteratee, memo, context){
			return underscore.reduceRight(list, iteratee, memo, context);
		},
		//在list中逐项查找，返回第一个通过predicate迭代函数真值检测的元素值，如果没有值传递给测试迭代器将返回undefined。 如果找到匹配的元素，函数将立即返回，不会遍历整个list
		//find(list, predicate, [context])
		/*
		 var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
		 => 2
		 */
		find : function(list, predicate, context){
			return underscore.find(list, predicate, context);
		},
		//遍历list中的每个值，返回包含所有通过predicate真值检测的元素值//如果存在原生filter方法，则用原生的filter方法
		//filter(list, predicate, [context])
		/*
		 var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
		 => [2, 4, 6]
		 */
		filter : function(list, predicate, context){
			return underscore.filter(list, predicate, context);
		},
		//遍历list中的每一个值，返回一个数组，这个数组包含properties所列出的属性的所有的 键 - 值对
		//where(list, properties)
		/*
		 _.where(listOfPlays, {author: "Shakespeare", year: 1611});
		 => [{title: "Cymbeline", author: "Shakespeare", year: 1611},
		 {title: "The Tempest", author: "Shakespeare", year: 1611}]
		 */
		where : function(list, properties){
			return underscore.where(list, properties);
		},
		//遍历整个list，返回匹配 properties参数所列出的所有 键 - 值 对的第一个值//如果没有找到匹配的属性，或者list是空的，那么将返回undefined。
		//findWhere(list, properties)
		/*
		 _.findWhere(publicServicePulitzers, {newsroom: "The New York Times"});
		 => {year: 1918, newsroom: "The New York Times",
		 reason: "For its public service in publishing in full so many official reports,
		 documents and speeches by European statesmen relating to the progress and
		 conduct of the war."}
		 */
		findWhere : function(list, properties){
			return underscore.findWhere(list, properties);
		},
		//返回list中没有通过predicate真值检测的元素集合，与filter相反
		//reject(list, predicate, [context])
		/*
		 var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
		 => [1, 3, 5]
		 */
		reject : function(list, predicate, context){
			return underscore.reject(list, predicate, context);
		},
		//如果list中的所有元素都通过predicate的真值检测就返回true//如果存在原生的every方法，就使用原生的every
		//every(list, [predicate], [context])
		/*
		 _.every([true, 1, null, 'yes'], _.identity);
		 => false
		 */
		every : function(list, predicate, context){
			return underscore.every(list, predicate, context);
		},
		//如果list中有任何一个元素通过 predicate 的真值检测就返回true。一旦找到了符合条件的元素, 就直接中断对list的遍历//如果存在原生的some方法，就使用原生的some
		//some(list, [predicate], [context])
		/*
		 _.some([null, 0, 'yes', false]);
		 => true
		 */
		some : function(list, predicate, context){
			return underscore.some(list, predicate, context);
		},
		//如果list包含指定的value则返回true。如果list 是数组，内部使用indexOf判断。使用fromIndex来给定开始检索的索引位置
		//contains(list, value, [fromIndex])
		/*
		 _.contains([1, 2, 3], 3);
		 => true
		 */
		contains : function(list, value, fromIndex){
			return underscore.contains(list, value, fromIndex);
		},
		//在list的每个元素上执行methodName方法。 任何传递给invoke的额外参数，invoke都会在调用methodName方法的时候传递给它
		//invoke(list, methodName, *arguments)
		/*
		 _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
		 => [[1, 5, 7], [1, 2, 3]]
		 */
		invoke : function(){
			return underscore.invoke.apply(underscore,arguments);
		},
		//pluck也许是map最常使用的用例模型的简化版本，即萃取数组对象中某属性值，返回一个数组
		//pluck(list, propertyName)
		/*
		 var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
		 _.pluck(stooges, 'name');
		 => ["moe", "larry", "curly"]
		 */
		pluck : function(list, propertyName){
			return underscore.pluck(list, propertyName);
		},
		//返回list中的最大值。如果传递iteratee参数，iteratee将作为list中每个值的排序依据。如果list为空，将返回-Infinity，所以你可能需要事先用isEmpty检查 list
		//max(list, [iteratee], [context])
		/*
		 var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
		 _.max(stooges, function(stooge){ return stooge.age; });
		 => {name: 'curly', age: 60};
		 */
		max : function(list, iteratee, context){
			return underscore.max(list, iteratee, context);
		},
		//返回list中的最小值。如果传递iteratee参数，iteratee将作为list中每个值的排序依据。如果list为空，将返回-Infinity，所以你可能需要事先用isEmpty检查 list
		//min(list, [iteratee], [context])
		/*
		 var numbers = [10, 5, 100, 2, 1000];
		 _.min(numbers);
		 => 2
		 */
		min : function(list, iteratee, context){
			return underscore.min(list, iteratee, context);
		},
		//返回一个排序后的list拷贝副本。如果传递iteratee参数，iteratee将作为list中每个值的排序依据。迭代器也可以是字符串的属性的名称进行排序的(比如 length)
		//sortBy(list, iteratee, [context])
		/*
		 _.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
		 => [5, 4, 6, 3, 1, 2]

		 var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
		 _.sortBy(stooges, 'name');
		 => [{name: 'curly', age: 60}, {name: 'larry', age: 50}, {name: 'moe', age: 40}];
		 */
		sortBy : function(list, iteratee, context){
			return underscore.sortBy(list, iteratee, context);
		},
		//把一个集合分组为多个集合，通过 iterator 返回的结果进行分组. 如果 iterator 是一个字符串而不是函数, 那么将使用 iterator 作为各元素的属性名来对比进行分组
		//groupBy(list, iteratee, [context])
		/*
		 _.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
		 => {1: [1.3], 2: [2.1, 2.4]}

		 _.groupBy(['one', 'two', 'three'], 'length');
		 => {3: ["one", "two"], 5: ["three"]}
		 */
		groupBy : function(list, iteratee, context){
			return underscore.groupBy(list, iteratee, context);
		},
		//给定一个list，和 一个用来返回一个在列表中的每个元素键 的iterator 函数（或属性名）， 返回一个每一项索引的对象。和groupBy非常像，但是当你知道你的键是唯一的时候可以使用indexBy
		//indexBy(list, iteratee, [context])
		/*
		 var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];
		 _.indexBy(stooges, 'age');
		 => {
		 "40": {name: 'moe', age: 40},
		 "50": {name: 'larry', age: 50},
		 "60": {name: 'curly', age: 60}
		 }
		 */
		indexBy : function(list, iteratee, context){
			return underscore.indexBy(list, iteratee, context);
		},
		//排序一个列表组成一个组，并且返回各组中的对象的数量的计数。类似groupBy，但是不是返回列表的值，而是返回在该组中值的数目
		//countBy(list, iteratee, [context])
		/*
		 _.countBy([1, 2, 3, 4, 5], function(num) {
		 return num % 2 == 0 ? 'even': 'odd';
		 });
		 => {odd: 3, even: 2}
		 */
		countBy : function(list, iteratee, context){
			return underscore.countBy(list, iteratee, context);
		},
		//返回一个随机乱序的 list 副本, 使用 Fisher-Yates shuffle 来进行随机乱序.
		//shuffle(list)
		/*
		 _.shuffle([1, 2, 3, 4, 5, 6]);
		 => [4, 1, 6, 3, 5, 2]
		 */
		shuffle : function(list){
			return underscore.shuffle(list);
		},
		//从 list中产生一个随机样本。传递一个数字表示从list中返回n个随机元素。否则将返回一个单一的随机项
		//sample(list, [n])
		/*
		 _.sample([1, 2, 3, 4, 5, 6]);
		 => 4

		 _.sample([1, 2, 3, 4, 5, 6], 3);
		 => [1, 6, 2]
		 */
		sample : function(list, n){
			return underscore.sample(list, n);
		},
		//把list(任何可以迭代的对象)转换成一个数组，在转换 arguments 对象时非常有用
		//toArray(list)
		/*
		 (function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
		 => [2, 3, 4]
		 */
		toArray : function(list){
			return underscore.toArray(list);
		},
		//返回list的长度
		//size(list)
		/*
		 _.size({one: 1, two: 2, three: 3});
		 => 3
		 */
		size : function(list){
			return underscore.size(list);
		},
		//拆分一个数组（array）为两个数组：  第一个数组其元素都满足predicate迭代函数， 而第二个的所有元素均不能满足predicate迭代函数
		//partition(array, predicate)
		/*
		 _.partition([0, 1, 2, 3, 4, 5], isOdd);
		 => [[1, 3, 5], [0, 2, 4]]
		 */
		partition : function(array, predicate){
			return underscore.partition(array, predicate);
		}
	});
})(window);
/**
 * @author yangyj
 * @desc dom操作
 * @depends widget.js
 */
(function(window){
	vita.widget("domUtil",{
		//生成dom对象，入参：根节点字符串，例：root
		createDom : function(data){
			var dom, dataType = "tag", str;
			//如果入参不是字符串，返回null
			if(!data || typeof data !== "string"){
				return null;
			}
			//判断入参是否一个xml字符串
			if(data.charAt(0) == "<"){
				if(data.charAt(data.length - 1) != ">"){
					return null;
				}
				var tagFirst = data.substring(1,data.indexOf(">"));
				var tagEnd = data.substring(data.lastIndexOf("<") + 2,data.length - 1);
				if(tagFirst != tagEnd){
					//根节点标签不匹配
					return null;
				}else{
					dataType = "xml";
				}
			}
			//生成要转成dom的xml
			if(dataType == "tag"){
				str = "<"+data+"/>";
			}else{
				str = data;
			}
			//生成dom
			try{
				if(window.DOMParser){
					dom = (new DOMParser()).parseFromString(str , "text/xml");
				}else{
					dom = new ActiveXObject("Microsoft.XMLDOM");
					dom.async = "false";
					dom.loadXML(str);
				}				
			}catch(e){
				dom = null;
				throw new Error("创建dom对象失败：" + data);
			}
			return dom;
		},
		//dom对象转成string
		domToString : function(dom){
			var re = "";
			if (!dom) {
				return null;
			}
			if(window.XMLSerializer){
				re = (new XMLSerializer()).serializeToString(dom);
			}else{
				re = dom.xml;
			}
			return re;
		}
	});
})(window);
/**
 * @author yangyj
 * @desc 函数操作
 * @depends widget.js
 */
(function(window){
	var underscore = vita.widget.require("underscore");
	vita.widget("functionUtil",{
		//绑定函数 function 到对象 object 上, 也就是无论何时调用函数, 函数里的 this 都指向这个 object.任意可选参数 arguments 可以传递给函数 function
		//bind(function, object, *arguments)
		/*
		 var func = function(greeting){ return greeting + ': ' + this.name };
		 func = _.bind(func, {name: 'moe'}, 'hi');
		 func();
		 => 'hi: moe'
		 */
		bind : function(){
			return underscore.bind.apply(underscore,arguments);
		},
		//把methodNames参数指定的一些方法绑定到object上，这些方法就会在对象的上下文环境中执行。绑定函数用作事件处理函数时非常便利，否则函数被调用时this一点用也没有。methodNames参数是必须的。
		//bindAll(object, *methodNames)
		/*
		 var buttonView = {
		 label  : 'underscore',
		 onClick: function(){ alert('clicked: ' + this.label); },
		 onHover: function(){ console.log('hovering: ' + this.label); }
		 };
		 _.bindAll(buttonView, 'onClick', 'onHover');
		 // When the button is clicked, this.label will have the correct value.
		 jQuery('#underscore_button').bind('click', buttonView.onClick);
		 */
		bindAll : function(){
			return underscore.bindAll.apply(underscore,arguments);
		},
		//局部应用一个函数填充在任意个数的 arguments，不改变其动态this值。和bind方法很相近。你可以传递_ 给arguments列表来指定一个不预先填充，但在调用时提供的参数。
		//partial(function, *arguments)
		/*
		 var subtract = function(a, b) { return b - a; };
		 sub5 = _.partial(subtract, 5);
		 sub5(20);
		 => 15

		 // Using a placeholder
		 subFrom20 = _.partial(subtract, _, 20);
		 subFrom20(5);
		 => 15
		 */
		partial : function(){
			return underscore.partial.apply(underscore,arguments);
		},
		//Memoizes方法可以缓存某函数的计算结果。对于耗时较长的计算是很有帮助的。如果传递了 hashFunction 参数，就用 hashFunction 的返回值作为key存储函数的计算结果。hashFunction 默认使用function的第一个参数作为key。memoized值的缓存可作为返回函数的cache属性。
		//memoize(function, [hashFunction])
		/*
		 var fibonacci = _.memoize(function(n) {
		 return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
		 });
		 */
		memoize : function(func, hashFunction){
			return underscore.memoize(func, hashFunction);
		},
		//类似setTimeout，等待wait毫秒后调用function。如果传递可选的参数arguments，当函数function执行时， arguments 会作为参数传入。
		//delay(function, wait, *arguments)
		/*
		 var log = _.bind(console.log, console);
		 _.delay(log, 1000, 'logged later');
		 => 'logged later' // Appears after one second.
		 */
		delay : function(){
			return underscore.delay.apply(underscore,arguments);
		},
		//延迟调用function直到当前调用栈清空为止，类似使用延时为0的setTimeout方法。对于执行开销大的计算和无阻塞UI线程的HTML渲染时候非常有用。 如果传递arguments参数，当函数function执行时， arguments 会作为参数传入。
		//defer(function, *arguments)
		/*
		 _.defer(function(){ alert('deferred'); });
		 // Returns from the function before the alert runs.
		 */
		defer : function(){
			return underscore.defer.apply(underscore,arguments);
		},
		//创建并返回一个像节流阀一样的函数，当重复调用函数的时候，至少每隔 wait毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。
		//throttle(function, wait, [options])
		/*
		 var throttled = _.throttle(updatePosition, 100);
		 $(window).scroll(throttled);
		 */
		throttle : function(func, wait, options){
			return underscore.throttle(func, wait, options);
		},
		//返回 function 函数的防反跳版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后. 对于必须在一些输入（多是一些用户操作）停止到达之后执行的行为有帮助。 例如: 渲染一个Markdown格式的评论预览, 当窗口停止改变大小之后重新计算布局, 等等.
		//debounce(function, wait, [immediate])
		/*
		 var lazyLayout = _.debounce(calculateLayout, 300);
		 $(window).resize(lazyLayout);
		 */
		debounce : function(func, wait, immediate){
			return underscore.debounce(func, wait, immediate);
		},
		//创建一个只能调用一次的函数。重复调用改进的方法也没有效果，只会返回第一次执行时的结果。 作为初始化函数使用时非常有用, 不用再设一个boolean值来检查是否已经初始化完成.
		//once(function)
		/*
		 var initialize = _.once(createApplication);
		 initialize();
		 initialize();
		 // Application is only created once.
		 */
		once : function(func){
			return underscore.once(func);
		},
		//创建一个函数, 只有在运行了 count 次之后才有效果. 在处理同组异步请求返回结果时, 如果你要确保同组里所有异步请求完成之后才 执行这个函数, 这将非常有用。
		//after(count, function)
		/*
		 var renderNotes = _.after(notes.length, render);
		 _.each(notes, function(note) {
		 note.asyncSave({success: renderNotes});
		 });
		 // renderNotes is run once, after all notes have saved.
		 */
		after : function(count, func){
			return underscore.after(count, func);
		},
		//创建一个函数,调用不超过count 次。 当count已经达到时，最后一个函数调用的结果将被记住并返回。
		//before(count, function)
		/*
		 var monthlyMeeting = _.before(3, askForRaise);
		 monthlyMeeting();
		 monthlyMeeting();
		 monthlyMeeting();
		 // the result of any subsequent calls is the same as the second call
		 */
		before : function(count, func){
			return underscore.before(count, func);
		},
		//将第一个函数 function 封装到函数 wrapper 里面, 并把函数 function 作为第一个参数传给 wrapper. 这样可以让 wrapper 在 function 运行之前和之后 执行代码, 调整参数然后附有条件地执行
		//wrap(function, wrapper)
		/*
		 var hello = function(name) { return "hello: " + name; };
		 hello = _.wrap(hello, function(func) {
		 return "before, " + func("moe") + ", after";
		 });
		 hello();
		 => 'before, hello: moe, after'
		 */
		wrap : function(func, wrapper){
			return underscore.wrap(func, wrapper);
		},
		//返回一个新的predicate函数的否定版本。
		//negate(predicate)
		/*
		 var isFalsy = _.negate(Boolean);
		 _.find([-2, -1, 0, 1, 2], isFalsy);
		 => 0
		 */
		negate : function(predicate){
			return underscore.negate(predicate);
		},
		//返回函数集 functions 组合后的复合函数, 也就是一个函数执行完之后把返回的结果再作为参数赋给下一个函数来执行. 以此类推. 在数学里, 把函数 f(), g(), 和 h() 组合起来可以得到复合函数 f(g(h()))。
		//compose(*functions)
		/*
		 var greet    = function(name){ return "hi: " + name; };
		 var exclaim  = function(statement){ return statement.toUpperCase() + "!"; };
		 var welcome = _.compose(greet, exclaim);
		 welcome('moe');
		 => 'hi: MOE!'
		 */
		compose : function(){
			return underscore.compose.apply(underscore,arguments);
		}
	});
})(window);
/**
 * @author yangyj
 * @desc 对象操作
 * @depends widget.js
 */
(function(window){
	var underscore = vita.widget.require("underscore");
	vita.widget("objectUtil",{
		//检索object拥有的所有可枚举属性的名称。
		//keys(object)
		/*
		 _.keys({one: 1, two: 2, three: 3});
		 => ["one", "two", "three"]
		 */
		keys : function(object){
			return underscore.keys(object);
		},
		//检索object拥有的和继承的所有属性的名称。
		//allKeys(object)
		/*
		 function Stooge(name) {
		 this.name = name;
		 }
		 Stooge.prototype.silly = true;
		 _.allKeys(new Stooge("Moe"));
		 => ["name", "silly"]
		 */
		allKeys : function(object){
			return underscore.allKeys(object);
		},
		//返回object对象所有的属性值。
		//values(object)
		/*
		 _.values({one: 1, two: 2, three: 3});
		 => [1, 2, 3]
		 */
		values : function(object){
			return underscore.values(object);
		},
		//类似于map，但是这用于对象。转换每个属性的值。
		//mapObject(object, iteratee, [context])
		/*
		 _.mapObject({start: 5, end: 12}, function(val, key) {
		 return val + 5;
		 });
		 => {start: 10, end: 17}
		 */
		mapObject : function(object, iteratee, context){
			return underscore.mapObject(object, iteratee, context);
		},
		//把一个对象转变为一个[key, value]形式的数组。
		//pairs(object)
		/*
		 _.pairs({one: 1, two: 2, three: 3});
		 => [["one", 1], ["two", 2], ["three", 3]]
		 */
		pairs : function(object){
			return underscore.pairs(object);
		},
		//返回一个object副本，使其键（keys）和值（values）对换。对于这个操作，必须确保object里所有的值都是唯一的且可以序列号成字符串.
		//invert(object)
		/*
		 _.invert({Moe: "Moses", Larry: "Louis", Curly: "Jerome"});
		 => {Moses: "Moe", Louis: "Larry", Jerome: "Curly"};
		 */
		invert : function(object){
			return underscore.invert(object);
		},
		//创建具有给定原型的新对象， 可选附加props 作为 own的属性。 基本上，和Object.create一样， 但是没有所有的属性描述符。
		//create(prototype, props)
		/*
		 var moe = _.create(Stooge.prototype, {name: "Moe"});
		 */
		create : function(prototype, props){
			return underscore.create(prototype, props);
		},
		//返回一个对象里所有的方法名, 而且是已经排序的 — 也就是说, 对象里每个方法(属性值是一个函数)的名称.
		//functions(object)
		/*
		 _.functions(_);
		 => ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...
		 */
		functions : function(object){
			return underscore.functions(object);
		},
		//返回一个通过谓词测试的对象的第一个键
		//findKey(object, predicate, [context])
		findKey : function(object, predicate, context){
			return underscore.findKey(object, predicate, context);
		},
		//复制source对象中的所有属性覆盖到destination对象上，并且返回 destination 对象. 复制是按顺序的, 所以后面的对象属性会把前面的对象属性覆盖掉(如果有重复).
		//extend(destination, *sources)
		/*
		 _.extend({name: 'moe'}, {age: 50});
		 => {name: 'moe', age: 50}
		 */
		extend : function(){
			return underscore.extend.apply(underscore,arguments);
		},
		//类似于 extend, 但只复制自己的属性覆盖到目标对象。不包括继承过来的属性
		//extendOwn(destination, *sources)
		extendOwn : function(){
			return underscore.extendOwn.apply(underscore,arguments);
		},
		//返回一个object副本，只过滤出keys(有效的键组成的数组)参数指定的属性值。或者接受一个判断函数，指定挑选哪个key。
		//pick(object, *keys)
		/*
		 _.pick({name: 'moe', age: 50, userid: 'moe1'}, 'name', 'age');
		 => {name: 'moe', age: 50}
		 _.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
		 return _.isNumber(value);
		 });
		 => {age: 50}
		 */
		pick : function(){
			return underscore.pick.apply(underscore,arguments);
		},
		//返回一个object副本，只过滤出除去keys(有效的键组成的数组)参数指定的属性值。 或者接受一个判断函数，指定忽略哪个key。
		//omit(object, *keys)
		/*
		 _.omit({name: 'moe', age: 50, userid: 'moe1'}, 'userid');
		 => {name: 'moe', age: 50}
		 _.omit({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
		 return _.isNumber(value);
		 });
		 => {name: 'moe', userid: 'moe1'}
		 */
		omit : function(){
			return underscore.omit.apply(underscore,arguments);
		},
		//用defaults对象填充object 中的undefined属性。 并且返回这个object。一旦这个属性被填充，再使用defaults方法将不会有任何效果。
		//defaults(object, *defaults)
		/*
		 var iceCream = {flavor: "chocolate"};
		 _.defaults(iceCream, {flavor: "vanilla", sprinkles: "lots"});
		 => {flavor: "chocolate", sprinkles: "lots"}
		 */
		defaults : function(){
			return underscore.defaults.apply(underscore,arguments);
		},
		//创建 一个浅复制（浅拷贝）的克隆object。任何嵌套的对象或数组都通过引用拷贝，不会复制。
		//clone(object)
		/*
		 _.clone({name: 'moe'});
		 => {name: 'moe'};
		 */
		clone : function(object){
			return underscore.clone(object);
		},
		//用 object作为参数来调用函数interceptor，然后返回object。这种方法的主要意图是作为函数链式调用 的一环, 为了对此对象执行操作并返回对象本身。
		//tap(object, interceptor)
		/*
		 _.chain([1,2,3,200])
		 .filter(function(num) { return num % 2 == 0; })
		 .tap(alert)
		 .map(function(num) { return num * num })
		 .value();
		 => // [2, 200] (alerted)
		 => [4, 40000]
		 */
		tap : function(object, interceptor){
			return underscore.tap(object, interceptor);
		},
		//对象是否包含给定的键吗？等同于object.hasOwnProperty(key)，但是使用hasOwnProperty 函数的一个安全引用，以防意外覆盖。
		//has(object, key)
		/*
		 _.has({a: 1, b: 2, c: 3}, "b");
		 => true
		 */
		has : function(object, key){
			return underscore.has(object, key);
		},
		//返回一个函数，这个函数返回任何传入的对象的key属性。
		//property(key)
		/*
		 var stooge = {name: 'moe'};
		 'moe' === _.property('name')(stooge);
		 => true
		 */
		property : function(key){
			return underscore.property(key);
		},
		//和_.property相反。需要一个对象，并返回一个函数,这个函数将返回一个提供的属性的值。
		//propertyOf(object)
		/*
		 var stooge = {name: 'moe'};
		 _.propertyOf(stooge)('name');
		 => 'moe'
		 */
		propertyOf : function(object){
			return underscore.propertyOf(object);
		},
		//返回一个断言函数，这个函数会给你一个断言可以用来辨别给定的对象是否匹配attrs指定键/值属性。
		//matcher(attrs)
		/*
		 var ready = _.matcher({selected: true, visible: true});
		 var readyToGoList = _.filter(list, ready);
		 */
		matcher : function(attrs){
			return underscore.matcher(attrs);
		},
		//执行两个对象之间的优化深度比较，确定他们是否应被视为相等。
		//isEqual(object, other)
		/*
		 var stooge = {name: 'moe', luckyNumbers: [13, 27, 34]};
		 var clone  = {name: 'moe', luckyNumbers: [13, 27, 34]};
		 stooge == clone;
		 => false
		 _.isEqual(stooge, clone);
		 => true
		 */
		isEqual : function(object, other){
			return underscore.isEqual(object, other);
		},
		//告诉你properties中的键和值是否包含在object中。
		//isMatch(object, properties)
		/*
		 var stooge = {name: 'moe', age: 32};
		 _.isMatch(stooge, {age: 32});
		 => true
		 */
		isMatch : function(object, properties){
			return underscore.isMatch(object, properties);
		},
		//如果object 不包含任何值(没有可枚举的属性)，返回true。 对于字符串和类数组（array-like）对象，如果length属性为0，那么_.isEmpty检查返回true。
		//isEmpty(object)
		/*
		 _.isEmpty([1, 2, 3]);
		 => false
		 _.isEmpty({});
		 => true
		 */
		isEmpty : function(object){
			return underscore.isEmpty(object);
		},
		//如果object是一个DOM元素，返回true。
		//isElement(object)
		/*
		 _.isElement(jQuery('body')[0]);
		 => true
		 */
		isElement : function(object){
			return underscore.isElement(object);
		},
		//如果object是一个数组，返回true。
		//isArray(object)
		/*
		 (function(){ return _.isArray(arguments); })();
		 => false
		 _.isArray([1,2,3]);
		 => true
		 */
		isArray : function(object){
			return underscore.isArray(object);
		},
		//如果object是一个对象，返回true。需要注意的是JavaScript数组和函数是对象，字符串和数字不是。
		//isObject(value)
		/*
		 _.isObject({});
		 => true
		 _.isObject(1);
		 => false
		 */
		isObject : function(value){
			return underscore.isObject(value);
		},
		//如果object是一个参数对象，返回true。
		//isArguments(object)
		/*
		 (function(){ return _.isArguments(arguments); })(1, 2, 3);
		 => true
		 _.isArguments([1,2,3]);
		 => false
		 */
		isArguments : function(object){
			return underscore.isArguments(object);
		},
		//如果object是一个函数（Function），返回true。
		//isFunction(object)
		/*
		 _.isFunction(alert);
		 => true
		 */
		isFunction : function(object){
			return underscore.isFunction(object);
		},
		//如果object是一个字符串，返回true。
		//isString(object)
		/*
		 _.isString("moe");
		 => true
		 */
		isString : function(object){
			return underscore.isString(object);
		},
		//如果object是一个数值，返回true (包括 NaN)。
		//isNumber(object)
		/*
		 _.isNumber(8.4 * 5);
		 => true
		 */
		isNumber : function(object){
			return underscore.isNumber(object);
		},
		//如果object是一个有限的数字，返回true。
		//isFinite(object)
		/*
		 _.isFinite(-101);
		 => true

		 _.isFinite(-Infinity);
		 => false
		 */
		isFinite : function(object){
			return underscore.isFinite(object);
		},
		//如果object是一个布尔值，返回true，否则返回false。
		//isBoolean(object)
		/*
		 _.isBoolean(null);
		 => false
		 */
		isBoolean : function(object){
			return underscore.isBoolean(object);
		},
		//Returns true if object is a Date.
		//isDate(object)
		/*
		 _.isDate(new Date());
		 => true
		 */
		isDate : function(object){
			return underscore.isDate(object);
		},
		//如果object是一个正则表达式，返回true。
		//isRegExp(object)
		/*
		 _.isRegExp(/moe/);
		 => true
		 */
		isRegExp : function(object){
			return underscore.isRegExp(object);
		},
		//如果object继承至 Error 对象，那么返回 true。
		//isError(object)
		/*
		 try {
		 throw new TypeError("Example");
		 } catch (o_O) {
		 _.isError(o_O)
		 }
		 => true
		 */
		isError : function(object){
			return underscore.isError(object);
		},
		//如果object是 NaN，返回true。注意： 这和原生的isNaN 函数不一样，如果变量是undefined，原生的isNaN 函数也会返回 true 。
		//isNaN(object)
		/*
		 _.isNaN(NaN);
		 => true
		 isNaN(undefined);
		 => true
		 _.isNaN(undefined);
		 => false
		 */
		isNaN : function(object){
			return underscore.isNaN(object);
		},
		//如果object的值是 null，返回true。
		//isNull(object)
		/*
		 _.isNull(null);
		 => true
		 _.isNull(undefined);
		 => false
		 */
		isNull : function(object){
			return underscore.isNull(object);
		},
		//如果value是undefined，返回true。
		//isUndefined(value)
		/*
		 _.isUndefined(window.missingVariable);
		 => true
		 */
		isUndefined : function(value){
			return underscore.isUndefined(value);
		}
	});
})(window);
/**
 * @author yangyj
 * @desc 工具
 * @depends widget.js
 */
(function(window){
	var underscore = vita.widget.require("underscore");
	vita.widget("util",{
		//调用给定的迭代函数n次,每一次调用iterate传递index参数。生成一个返回值的数组。context将作为iterate的this。
		//times(n, iterate, [context])
		/*
		 _.times(2,function(n){ return this[n]; },[1,2]);
		 => [1, 2]
		 */
		times : function(n, iterate, context){
			return underscore.times(n, iterate, context);
		},
		//返回一个min 和 max之间的随机整数。如果你只传递一个参数，那么将返回0和这个参数之间的整数。
		//random(min, max)
		/*
		 _.random(0, 100);
		 => 42
		 */
		random : function(min, max){
			return underscore.random(min, max);
		},
		//为需要的客户端模型或DOM元素生成一个全局唯一的id。如果prefix参数存在， id 将附加给它。
		//uniqueId([prefix])
		/*
		 _.uniqueId('contact_');
		 => 'contact_104'
		 */
		uniqueId : function(prefix){
			return underscore.uniqueId(prefix);
		},
		//转义HTML字符串，替换&, <, >, ", ', 和 /字符。
		//escape(string)
		/*
		 _.escape('Curly, Larry & Moe');
		 => "Curly, Larry &amp; Moe"
		 */
		escape : function(string){
			return underscore.escape(string);
		},
		//和escape相反。转义HTML字符串，替换&, &lt;, &gt;, &quot;, &#96;, 和 &#x2F;字符。
		//unescape(string)
		/*
		 _.unescape('Curly, Larry &amp; Moe');
		 => "Curly, Larry & Moe"
		 */
		unescape : function(string){
			return underscore.unescape(string);
		},
		//如果指定的property 的值是一个函数，那么将在object上下文内调用它;否则，返回它。如果提供默认值，并且属性不存在，那么默认值将被返回。如果设置defaultValue是一个函数，它的结果将被返回。
		//result(object, property, [defaultValue])
		/*
		 var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
		 _.result(object, 'cheese');
		 => "crumpets"
		 _.result(object, 'stuff');
		 => "nonsense"
		 _.result(object, 'meat', 'ham');
		 => "ham"
		 */
		result : function(object, property, defaultValue){
			return underscore.result(object, property, defaultValue);
		},
		//一个优化的方式来获得一个当前时间的整数时间戳。可用于实现定时/动画功能。
		//now()
		/*
		 _.now();
		 => 1392066795351
		 */
		now : function(){
			return underscore.now();
		},
		//将 JavaScript 模板编译为可以用于页面呈现的函数, 对于通过JSON数据源生成复杂的HTML并呈现出来的操作非常有用。 模板函数可以使用 <%= … %>插入变量, 也可以用<% … %>执行任意的 JavaScript 代码。 如果您希望插入一个值, 并让其进行HTML转义,请使用<%- … %>。 当你要给模板函数赋值的时候，可以传递一个含有与模板对应属性的data对象 。 如果您要写一个一次性的, 您可以传对象 data 作为第二个参数给模板 template 来直接呈现, 这样页面会立即呈现而不是返回一个模板函数. 参数 settings 是一个哈希表包含任何可以覆盖的设置 _.templateSettings.
		//template(templateString, [settings])
		/*
		 var compiled = _.template("hello: <%= name %>");
		 compiled({name: 'moe'});
		 => "hello: moe"

		 var template = _.template("<b><%- value %></b>");
		 template({value: '<script>'});
		 => "<b>&lt;script&gt;</b>"
		 */
		template : function(templateString, settings){
			return underscore.template(templateString, settings);
		}
	});
})(window);
/**
 * @author
 * @desc 日期操作
 * @depends widget.js
 */
(function(window){
    vita.widget("dateUtil",{
        /**
         * 显示当前对象名称路径
         * @method toString
         * @return {String} 'Rose.date'
         */
        toString : function() {
            return 'Rose.date';
        },
        /**
         * 将小于10数字前加0
         * @method _zeroCompletion
         * @param {Number} time 时间
         * @return {String}
         */
        _zeroCompletion : function(time) {
            if (time < 10) {
                return '0' + time;
            }
            return time + '';
        },
        /**
         * 将秒转换为时间hh:mm:ss格式
         * @method secs2Time
         * @param {Number} secs 秒
         * @return {String} 格式化时间字符串'00:00:00'
         */
        secs2Time : function(secs) {
            if (secs < 0) {
                secs = 0;
            }
            secs = parseInt(secs, 10);
            var hours = Math.floor(secs / 3600), mins = Math
                .floor((secs % 3600) / 60), sec = secs % 3600 % 60;
            return this._zeroCompletion(hours) + ':' + this._zeroCompletion(mins)
                + ':' + this._zeroCompletion(sec);
        },
        /**
         * 格式化日期时间字符串
         * @method dateTime2str
         * @param {Date} dt 日期对象
         * @param {String} fmt 格式化字符串，如：'yyyy-MM-dd hh:mm:ss'
         * @return {String} 格式化后的日期时间字符串
         */
        dateTime2str : function(dt, fmt) {
            var z = {
                M : dt.getMonth() + 1,
                d : dt.getDate(),
                h : dt.getHours(),
                m : dt.getMinutes(),
                s : dt.getSeconds()
            };
            fmt = fmt.replace(/(M+|d+|h+|m+|s+)/g, function(v) {
                return ((v.length > 1 ? "0" : "") + eval('z.' + v.slice(-1)))
                    .slice(-2);
            });
            return fmt.replace(/(y+)/g, function(v) {
                return dt.getFullYear().toString().slice(-v.length);
            });
        },
        /**
         * 根据日期时间格式获取获取当前日期时间
         * @method dateTimeWrapper
         * @param {String} fmt 日期时间格式，如："yyyy-MM-dd hh:mm:ss";
         * @return {String} 格式化后的日期时间字符串
         */
        dateTimeWrapper : function(fmt) {
            if (arguments[0])
                fmt = arguments[0];
            return this.dateTime2str(new Date(), fmt);
        },
        /**
         * 获取当前日期时间
         * @method getDatetime
         * @param {String} fmt [optional,default='yyyy-MM-dd hh:mm:ss'] 日期时间格式。
         * @return {String} 格式化后的日期时间字符串
         */
        getDatetime : function(fmt) {
            return this.dateTimeWrapper(fmt || 'yyyy-MM-dd hh:mm:ss');
        },
        /**
         * 获取当前日期时间+毫秒
         * @method getDatetimes
         * @param {String} fmt [optional,default='yyyy-MM-dd hh:mm:ss'] 日期时间格式。
         * @return {String} 格式化后的日期时间字符串
         */
        getDatetimes : function(fmt) {
            var dt = new Date();
            return this.dateTime2str(dt, fmt || 'yyyy-MM-dd hh:mm:ss') + '.'
                + dt.getMilliseconds();
        },
        /**
         * 获取当前日期（年-月-日）
         * @method getDate
         * @param {String} fmt [optional,default='yyyy-MM-dd'] 日期格式。
         * @return {String} 格式化后的日期字符串
         */
        getDate : function(fmt) {
            return this.dateTimeWrapper(fmt || 'yyyy-MM-dd');
        },
        /**
         * 获取当前时间（时:分:秒）
         * @method getTime
         * @param {String} fmt [optional,default='hh:mm:ss'] 日期格式。
         * @return {String} 格式化后的时间字符串
         */
        getTime : function(fmt) {
            return this.dateTimeWrapper(fmt || 'hh:mm:ss');
        },
        /**
         * 将标准日期时间格式转换为长整形格式
         * @param {String} datetime 为空或 yyyy-MM-dd hh:mm:ss 格式时间
         * @returns {number}
         */
        dateTime2Long : function(datetime){
            if(datetime && typeof datetime === "string"){
                return new Date(datetime.replace(/\-/g,'/')).getTime();
            }
            return new Date().getTime();
        },
        /**
         * 校验时间范围, 如果两个时间超过了days, 返回true。否则返回false
         * @param startDate	开始时间
         * @param endDate	结束时间
         * @param days		时间范围
         */
        checkWithin : function(startDate, endDate, days){
            if(days === void 0){
                days = 30; // 默认30天
            }
            if(startDate === void 0 || endDate === void 0){
                return false;
            }
            startDate.setDate(startDate.getDate() + days);
            if(startDate < endDate){
                return false;
            }
            return true;
        },
        /**
         * 将标准日期时间格式转换为时间
         * @param datetime 为空或 yyyy-MM-dd hh:mm:ss 格式时间
         */
        string2Date : function(datetime){
            if (datetime && typeof datetime === "string") {
                return new Date(datetime.replace(/\-/g, '/'));
            }
            return new Date();
        },
		/*
		 *****************
		 * 获取当期时间（格式：yyyy-mm-dd）
		 *****************
		 */
        getCurrentDate : function (){
            var day = new Date();
            var Year = 0;
            var Month = 0;
            var Day = 0;
            var CurrentDate = "";

            Year= day.getFullYear();
            Month= day.getMonth()+1;
            Day = day.getDate();
            CurrentDate += Year + "-";
            if (Month >= 10 ){
                CurrentDate += Month + "-";
            }else{
                CurrentDate += "0" + Month + "-";
            }

            if (Day >= 10 ){
                CurrentDate += Day ;
            } else {
                CurrentDate += "0" + Day ;
            }
            return CurrentDate;
        },
		/*
		 *****************
		 * 获得日期差值
		 *****************
		 */
        dateDiff : function (asStartDate,asEndDate){
            var miStart=Date.parse(asStartDate.replace(/\-/g,'/'));
            var miEnd=Date.parse(asEndDate.replace(/\-/g,'/'));
            return (miEnd-miStart)/(1000*24*3600);
        },
		/*
		 *****************
		 * 获取时间（格式：yyyy-mm-dd）
		 * +addDay, -subDay
		 *****************
		 */
        getNowFormatDate : function (addDay, subDay){
            var day = new Date();
            var Year = 0;
            var Month = 0;
            var Day = 0;
            var CurrentDate = "";

            Year= day.getFullYear();
            Month= day.getMonth()+1;
            Day = day.getDate() + addDay - subDay;
            CurrentDate += Year + "-";
            if (Month >= 10 ){
                CurrentDate += Month + "-";
            }else{
                CurrentDate += "0" + Month + "-";
            }

            if (Day >= 10 ){
                CurrentDate += Day ;
            } else {
                CurrentDate += "0" + Day ;
            }
            return CurrentDate;
        },
        /**
         * 获取几个月前的时间
         */
        getDateOfNMonthsAgo : function (t_time,nMonths){
            var myDate = new Date(Date.parse(t_time.replace(/-/g, "/")));
            var year=myDate.getFullYear();
            var month=myDate.getMonth();
            var day = myDate.getDate();
            if(nMonths >0){
                if(month>=nMonths){
                    month=month-nMonths+1;
                }
                else{
                    var t_year = parseInt(nMonths/12);
                    var y_year = nMonths%12;
                    if(month>=y_year){
                        year = year - t_year ;
                        month =  month - y_year;
                    }
                    else{
                        year = year - t_year -1;
                        month =  month - y_year +12;
                    }
                }
            }else{
                if(12-month>=-nMonths){
                    month = month-nMonths;
                }else{
                    var t_year = parseInt(nMonths/12);
                    var y_year = nMonths%12;
                    year = year - t_year ;
                    month =  month - y_year;
                }
            }
            var daysInNextMonth = daysInMonth(year, month);

            if (day > daysInNextMonth) {
                day = daysInNextMonth;
            }
            month++;
            if(month<10)
            {
                month="0"+month;
            }
            if(day<10)
            {
                day = "0"+day;
            }
            var newdate = year+"-"+month+"-"+day;
            return newdate;
        },
        /**
         * 判断这个月有几天
         */
        daysInMonth : function(year, month){
            if (month == 2) {
                if (year % 4 == 0 && year % 100 != 0)
                    return 29;
                else
                    return 28;
            } else if ((month <= 7 && month % 2 == 1) || (month > 7 && month % 2 == 0))
                return 31;
            else
                return 30;
        }
    });
})(window);